#include "FileSystem/File.h"

#include "Http/HttpClient.h"

BEGIN_GLSX_NAMESPACE

typedef struct ICacheFileHandle ICacheFileHandle;

struct ICacheFileHandle
{
	FILE* _file;
	char* _buffer;
	int _buffer_size;
	int _file_size;
	int _f_read_pos;
	int _b_read_pos;
	int _cache_size;
};

GlsxCommon_API std::wstring GetDirectoryPath( const std::wstring& strFilePath )
{
	if (strFilePath.empty())
	{
		return L"";
	}
	std::wstring::size_type pos = strFilePath.find_last_of(L"\\/");
	if (pos != std::wstring::npos)
	{
		return strFilePath.substr(0, pos+1);
	}
	return L"";
}

GlsxCommon_API std::wstring GetBaseName( const std::wstring& strFileName )
{
	if (strFileName.empty())
	{
		return L"";
	}
	std::wstring::size_type pos = strFileName.find_last_of(L"\\/");
	if (pos != std::wstring::npos)
	{
		std::wstring::size_type dot = strFileName.rfind(L".");
		if (dot != std::wstring::npos && pos < dot)
		{
			return strFileName.substr(pos + 1, dot - pos - 1);
		}
	}
	return strFileName;
}

GlsxCommon_API std::wstring GetExtension( const std::wstring& strFileName )
{
	if (strFileName.empty())
	{
		return L"";
	}
	std::wstring::size_type dot = strFileName.rfind(L".");
	if (dot != std::wstring::npos)
	{
		return strFileName.substr(dot+1);
	}
	return L"";
}

GlsxCommon_API bool CreateDirectories( const std::wstring& strDir )
{
	if (strDir.empty())
	{
		return false;
	}
	std::wstring strOutDir = strDir;


	if (strOutDir.at(strOutDir.size() - 1) != L'\\') strOutDir += L"\\";

	for(size_t i = 0; i<strOutDir.size(); ++ i)
	{
		// a separator can be /
		if (strOutDir.at(i) == L'\\' || strOutDir.at(i) == L'/') 
		{
			std::wstring strDir(strOutDir.begin(), strOutDir.begin() + i);
			// wince directory path start with \,  skip it
			if (strDir.empty())
			{
				continue;
			}
			DWORD dwAttr = GetFileAttributesW(strDir.c_str());

			if (dwAttr == 0xFFFFFFFF || !IsDirectory(strDir))
			{
				if(CreateDirectoryW(strDir.c_str(), 0) == FALSE) return false;
			}
		}
	}
	return true;
}


GlsxCommon_API bool ListDirectory( const std::wstring& strRootDir, std::vector<std::wstring>& vecOut, const std::wstring& strFilter/* = L"*.*"*/ )
{
	DWORD attr = GetFileAttributesW(strRootDir.c_str());

	if (attr == 0xFFFFFFFF || !(attr & FILE_ATTRIBUTE_DIRECTORY))
	{
		return false;
	}

	std::vector<std::wstring> vecStr;

	vecStr.push_back(strRootDir);

	while(vecStr.size() > 0)
	{
		std::wstring strSearchDir = vecStr.back();
		vecStr.pop_back();

		WIN32_FIND_DATAW fd;
		HANDLE hFind = FindFirstFileW((strSearchDir + L"\\" + strFilter).c_str(), &fd);

		do
		{
			if (hFind == INVALID_HANDLE_VALUE) break;

			if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
			{
				if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0)
				{
					continue;
				}
				vecStr.push_back(strSearchDir + L"\\" + fd.cFileName);
			}
			else
			{
				vecOut.push_back(strSearchDir + L"\\" + fd.cFileName);
			}
		}while(FindNextFile(hFind, &fd));

		if (hFind != INVALID_HANDLE_VALUE) FindClose(hFind);
	}

	return true;
}


GlsxCommon_API ULONG64 GetFileSize( const std::wstring& strFileName )
{
	if(strFileName.empty())
	{
		return 0;
	}

	WIN32_FILE_ATTRIBUTE_DATA fad;
	if (GetFileAttributesExW(strFileName.c_str(), GetFileExInfoStandard, &fad) == 0) 
		return 0;
	LARGE_INTEGER li;
	li.LowPart  = fad.nFileSizeLow;
	li.HighPart = fad.nFileSizeHigh;
	return li.QuadPart;
}

GlsxCommon_API bool IsFileExist( const std::wstring& strFileName )
{
	if(strFileName.empty())
	{
		return false;
	}
	DWORD attr = GetFileAttributesW(strFileName.c_str());
	return attr != 0xFFFFFFFF;
}

GlsxCommon_API bool IsFile( const std::wstring& strFileName )
{
	if(strFileName.empty())
	{
		return false;
	}
	DWORD attr = GetFileAttributesW(strFileName.c_str());
	return ((attr != 0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0);
}

GlsxCommon_API bool IsDirectory( const std::wstring& strDirectoryName )
{
	if(strDirectoryName.empty())
	{
		return false;
	}
	DWORD attr = GetFileAttributesW(strDirectoryName.c_str());
	return ((attr != 0xFFFFFFFF) && (attr & FILE_ATTRIBUTE_DIRECTORY));
}

GlsxCommon_API void CopyDirectory( const std::wstring& strSrcDir, const std::wstring& strDstDir )
{
	if (!IsFileExist(strSrcDir) || !IsDirectory(strSrcDir))
	{
		return;
	}
	if (!IsFileExist(strDstDir) || !IsDirectory(strDstDir))
	{
		CreateDirectories(strDstDir);
	}

	std::vector<std::wstring> vecStr;
	std::vector<std::wstring> vecCpy;

	vecStr.push_back(strSrcDir);
	vecCpy.push_back(strDstDir);

	while(vecStr.size() > 0)
	{
		std::wstring strSearchDir = vecStr.back();
		vecStr.pop_back();

		std::wstring strCpyDir = vecCpy.back();
		vecCpy.pop_back();

		WIN32_FIND_DATAW fd;
		HANDLE hFind = FindFirstFileW((strSearchDir + L"\\*.*").c_str(), &fd);

		do
		{
			if (hFind == INVALID_HANDLE_VALUE) break;

			std::wstring strTempCpyFile = strCpyDir + L"\\" + fd.cFileName;

			if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
			{
				if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0)
				{
					continue;
				}
				vecStr.push_back(strSearchDir + L"\\" + fd.cFileName);
				vecCpy.push_back(strTempCpyFile);
				if(!IsFileExist(strTempCpyFile) || !IsDirectory(strTempCpyFile))
				{
					CreateDirectories(strTempCpyFile);
				}
			}
			else
			{
				//vecOut.push_back(strSearchDir + L"\\" + fd.cFileName);
				CopyFileW((strSearchDir + L"\\" + fd.cFileName).c_str(), strTempCpyFile.c_str(), 0);
			}
		}while(FindNextFile(hFind, &fd));

		if (hFind != INVALID_HANDLE_VALUE) FindClose(hFind);
	}
}

GlsxCommon_API void WalkDirectory( const std::wstring& strRootDir, 
								  void(*FileFound)(const std::wstring& strFile, WIN32_FIND_DATAW* fd), 
								  bool bRecursive/* = true*/)
{
	if (!IsFileExist(strRootDir) || !IsDirectory(strRootDir) || !FileFound)
	{
		return;
	}

	std::vector<std::wstring> vecStr;
	vecStr.push_back(strRootDir);

	do
	{
		if (vecStr.size() <= 0)
		{
			break;
		}
		std::wstring strSearchDir = vecStr.back();
		vecStr.pop_back();

		WIN32_FIND_DATAW fd;

		
		HANDLE hFind = FindFirstFileW((strSearchDir + L"\\*.*").c_str(), &fd);
		do
		{
			if (hFind == INVALID_HANDLE_VALUE) break;
			if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0)
			{
				continue;
			}

			FileFound(strSearchDir + L"\\" + fd.cFileName, &fd);
		}while(FindNextFile(hFind, &fd));

		if (hFind != INVALID_HANDLE_VALUE) FindClose(hFind);
	}while(bRecursive);

	FileFound(L"", NULL);
}

GlsxCommon_API int DeleteDirectory( const std::wstring& strRootDir )
{
	if (!IsFileExist(strRootDir) || !IsDirectory(strRootDir))
	{
		return -1;
	}

	std::wstring strDirPath = strRootDir;

	// fix to a directory path
	if (strDirPath.at(strDirPath.size() -1 ) != L'\\' && strDirPath.at(strDirPath.size() -1 ) != L'/')
	{
		strDirPath += L"\\";
	}


	WIN32_FIND_DATA fd;
	HANDLE hdFind;
	hdFind = FindFirstFile((strDirPath + L"*.*").c_str(),&fd);
	if(hdFind != INVALID_HANDLE_VALUE)
	{
		do{
			if (wcscmp(fd.cFileName, L".") == 0 && wcscmp(fd.cFileName, L"..") == 0)
			{
				// need to delete?
			}
			else if(fd.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY )
			{
				DeleteDirectory(strDirPath + fd.cFileName);

				RemoveDirectory(strDirPath.c_str());
			}
			else
			{
				DeleteFile((strDirPath + fd.cFileName).c_str());
			}
		}while(FindNextFile(hdFind,&fd));
	}
	FindClose(hdFind);

	RemoveDirectory(strRootDir.c_str());

	return 0;
}


GlsxCommon_API int GsWriteFile( const std::wstring& strFile, const char* pData, int nLen )
{
	std::wstring strDirPath = GetDirectoryPath(strFile);
	if (!IsDirectory(strDirPath))
	{
		CreateDirectories(strDirPath);
	}
	HANDLE hFile = CreateFile(strFile.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL  );
	if(hFile == INVALID_HANDLE_VALUE)
	{
		return -1;
	}
	DWORD dwByteWrite = 0;
	int nRet = WriteFile(hFile, pData, nLen, &dwByteWrite, NULL);
	CloseHandle(hFile);
	return nRet == 0 ? -1 : 0;
}

GlsxCommon_API int GsSaveWriteFile( const std::wstring& strFile, const char* pData, int nLen )
{
	return GsWriteFile(strFile, pData, nLen);
}


GlsxCommon_API int GsReadFile( const std::wstring& strFile, std::string& strContent )
{
	int nSize  = (int)GetFileSize(strFile);
	if (nSize <= 0)
	{
		return -1;
	}

	HANDLE hFile = CreateFile(strFile.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL  );
	if(hFile == INVALID_HANDLE_VALUE)
	{
		return -1;
	}

	int nRet = 0;
	DWORD dwBytes = 0;
	DWORD nByteRead = 0;

	strContent.reserve((unsigned int)nSize);

	while(nSize > 0)
	{
		char buf[1024] = {0};
		int nRead = nSize >= 1024 ? 1023 : nSize;
		BOOL bRead = ReadFile(hFile, buf, nRead, &dwBytes, NULL);
		if (!bRead)
		{
			nRet = -1;
			goto read_end;
		}
		strContent += buf;
		nSize -= dwBytes;
		nByteRead  += dwBytes;
	}
read_end:	
	
	if (hFile)
	{
		CloseHandle(hFile);
	}

	return nRet;
}


static int cache_write_file(ICacheFileHandle* rwb, const char* pData, int nSize)
{
	if (!rwb || !rwb->_file)
	{
		return -1;
	}

	int m = 0;
	fseek(rwb->_file, 0, SEEK_END);
	while(m < nSize)
	{
		int w = fwrite(pData + m, 1, nSize - m, rwb->_file);
		if (w < 0)
		{
			printf("write data to file error: %d\n", GetLastError());
			return -1;
		}
		m += w;
	}
	return m;
}

GlsxCommon_API CacheFileHandle MMCacheFileOpen( const std::string& strFile, const char* szMode, int nCacheSize /*= 131072*/ )
{
	if (strFile.empty() || szMode == NULL)
	{
		printf("MMCacheFileOpen arg error\n");
		return NULL;
	}
	ICacheFileHandle* rwb = (ICacheFileHandle*)malloc(sizeof(ICacheFileHandle));
	if (rwb == NULL)
	{
		return 0;
	}
	memset(rwb, 0, sizeof(ICacheFileHandle));

	rwb->_file = fopen(strFile.c_str(), szMode);
	if (rwb->_file == NULL)
	{
		free(rwb);
		return NULL;
	}

	if (nCacheSize > 0)
	{
		
		if (nCacheSize < 1024)
		{
			nCacheSize = 1024;
		}
		rwb->_cache_size = nCacheSize;
		rwb->_buffer = (char*)malloc(nCacheSize);
		if (rwb->_buffer == NULL)
		{
			fclose(rwb->_file);
			free(rwb);
			rwb = NULL;
		}
	}
	fseek(rwb->_file, 0, SEEK_END);
	rwb->_file_size = ftell(rwb->_file);
	fseek(rwb->_file, 0, SEEK_SET);

	return rwb;
}


GlsxCommon_API void* MMCacheGetNativeHandle( CacheFileHandle h )
{
	return (h != NULL) ? (void*)(((ICacheFileHandle*)h)->_file) : NULL;
}


GlsxCommon_API size_t MMCacheGetFileSize( CacheFileHandle h )
{
	return (h != NULL) ? ((ICacheFileHandle*)h)->_file_size + ((ICacheFileHandle*)h)->_buffer_size : 0;
}

GlsxCommon_API void MMCacheFileClose( CacheFileHandle handle )
{
	ICacheFileHandle* rwb = (ICacheFileHandle*)handle;
	if (rwb == NULL)
	{
		return;
	}

	if(rwb->_cache_size > 0 && rwb->_buffer)
	{
		if (rwb->_buffer_size > 0 && rwb->_file)
		{
			cache_write_file(rwb, rwb->_buffer, rwb->_buffer_size);
		}
		free(rwb->_buffer);
	}
	if (rwb->_file)
	{
		fclose(rwb->_file);
	}
	free(rwb);
}

GlsxCommon_API int MMCacheFileRead( CacheFileHandle h, char* pBuf, int nSize )
{
	ICacheFileHandle* rwb = (ICacheFileHandle*)h;
	if (rwb == NULL || pBuf == NULL || rwb->_file == NULL)
	{
		return -1;
	}

	int read_size = 0;

	if (rwb->_cache_size <= 0 && rwb->_file)
	{
		fseek(rwb->_file, rwb->_f_read_pos, SEEK_SET);
		read_size = fread(pBuf, 1, nSize, rwb->_file);
		if (read_size > 0)
		{
			rwb->_f_read_pos += read_size;
		}
		return read_size;
	}

	int size = rwb->_file_size - rwb->_f_read_pos;

	if (size > 0 && rwb->_file)
	{
		fseek(rwb->_file, rwb->_f_read_pos, SEEK_SET);

		int nRead = size >= nSize ? nSize : size;
		read_size = fread(pBuf, 1, nRead, rwb->_file);
		if (read_size < 0)
		{
			printf("MMCacheFileRead read file data error\n");
			return -1;
		}
		rwb->_f_read_pos += read_size;
	}

	if (read_size < nSize && rwb->_buffer_size > 0)
	{
		int more_size = nSize - read_size;
		int buf_left_size = rwb->_buffer_size - rwb->_b_read_pos;
		int copy_size = buf_left_size >= more_size ? more_size : buf_left_size;

		memcpy(pBuf+read_size, rwb->_buffer + rwb->_b_read_pos, copy_size);
		rwb->_b_read_pos += copy_size;
		read_size += copy_size;
	}
	return read_size;
}

GlsxCommon_API int MMCacheFileWrite( CacheFileHandle h, const char* pData, int nSize )
{
	ICacheFileHandle* rwb = (ICacheFileHandle*)h;

	if (rwb == NULL || pData == NULL || nSize <= 0 )
	{
		return -1;
	}

	int write_size = 0;

	if (rwb->_cache_size <= 0)
	{
		if((write_size = cache_write_file(rwb, pData, nSize)) < 0)
		{
			return -1;
		}
		return write_size;
	}

	if (rwb->_buffer_size + nSize >= rwb->_cache_size)
	{

		if (rwb->_buffer_size > 0)
		{
			if((write_size = cache_write_file(rwb, rwb->_buffer, rwb->_buffer_size)) < 0)
			{
				return -1;
			}
			rwb->_file_size += write_size;
		}
		if((write_size = cache_write_file(rwb, pData, nSize)) < 0)
		{
			return -1;
		}

		rwb->_buffer_size = 0;
		rwb->_file_size += write_size;

		if (rwb->_b_read_pos > 0)
		{
			rwb->_f_read_pos += rwb->_b_read_pos;
			rwb->_b_read_pos = 0;
		}
	}
	else 
	{
		memcpy(rwb->_buffer + rwb->_buffer_size, pData, nSize);
		rwb->_buffer_size += nSize;
	}
	return nSize;
}

// GlsxCommon_API int MMCacheFileSeek( CacheFileHandle h, int offset, int orig )
// {
// 	return -1;
// }

GlsxCommon_API int MMCacheFileTell( CacheFileHandle h )
{
	ICacheFileHandle* rwb = (ICacheFileHandle*)h;
	return rwb->_file_size + rwb->_buffer_size;
}

GlsxCommon_API int MMCacheFileFlush( CacheFileHandle h )
{
	ICacheFileHandle* rwb = (ICacheFileHandle*)h;

	if (rwb == NULL)
	{
		return -1;
	}

	int write_size = 0;

	if((write_size = cache_write_file(rwb, rwb->_buffer, rwb->_buffer_size)) < 0)
	{
		return -1;
	}

	rwb->_buffer_size = 0;
	rwb->_file_size += write_size;

	if (rwb->_b_read_pos > 0)
	{
		rwb->_f_read_pos += rwb->_b_read_pos;
		rwb->_b_read_pos = 0;
	}

	return fflush(rwb->_file);
}

// static ULARGE_INTEGER DiskSpaceHelper(const std::wstring& strPath)
// {
// 	ULARGE_INTEGER ulRet;
// 
// 
// 
// 
// }

GlsxCommon_API int GetDiskFreeSpace( const std::wstring& strPath )
{
	std::wstring _strPath = strPath;
	std::wstring::size_type pos = strPath.find_last_of(L"\\/");
	if (pos != std::wstring::npos)
	{
		_strPath = strPath.substr(0, pos);
	}

	ULARGE_INTEGER FreeBytesAvailable;
	FreeBytesAvailable.QuadPart = 0;

// 	ULARGE_INTEGER TotalNumberOfBytes;
// 	TotalNumberOfBytes.QuadPart = 0;
// 
// 	ULARGE_INTEGER TotalNumberOfFreeBytes;
// 	TotalNumberOfFreeBytes.QuadPart = 0;

	if (GetDiskFreeSpaceEx(_strPath.c_str(),
		&FreeBytesAvailable, NULL/*&TotalNumberOfBytes*/, NULL/*&TotalNumberOfFreeBytes*/))
	{
		return (int)FreeBytesAvailable.QuadPart;
	}

	return 0;
}

GlsxCommon_API int GetDiskTotalSpace( const std::wstring& strPath )
{
	std::wstring _strPath = strPath;
	std::wstring::size_type pos = strPath.find_last_of(L"\\/");
	if (pos != std::wstring::npos)
	{
		_strPath = strPath.substr(0, pos);
	}

	//ULARGE_INTEGER FreeBytesAvailable;
	//FreeBytesAvailable.QuadPart = 0;

	 	ULARGE_INTEGER TotalNumberOfBytes;
	 	TotalNumberOfBytes.QuadPart = 0;
	// 
	// 	ULARGE_INTEGER TotalNumberOfFreeBytes;
	// 	TotalNumberOfFreeBytes.QuadPart = 0;

	if (GetDiskFreeSpaceEx(_strPath.c_str(),
		NULL/*&FreeBytesAvailable*/, &TotalNumberOfBytes, NULL/*&TotalNumberOfFreeBytes*/))
	{
		return (int)TotalNumberOfBytes.QuadPart;
	}

	return 0;
}



END_GLSX_NAMESPACE